﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.MemoryMappedFiles; // yeahha
using Ionic.Zip;

namespace AndroidADTHiddenHack
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void btHack_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(txtPathToFile.Text))
            {
                btBrowse_Click(btBrowse, EventArgs.Empty);
            }
            if (String.IsNullOrEmpty(txtPathToFile.Text))
            {
                return;
            }
            progressBar1.Maximum = 100;
            progressBar1.Minimum = 0;
            progressBar1.Value = 0;
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += bw_DoWork;
            bw.WorkerReportsProgress = true;
            bw.ProgressChanged += bw_ProgressChanged;
            bw.RunWorkerCompleted += bw_RunWorkerCompleted;
            bw.RunWorkerAsync(bw);
        }

        void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            btHack.Enabled = true;
            btBrowse.Enabled = true;
            label1.Text = "DONE!";            
        }

        class ProgressRpt
        {
            public String labelText;
            public BackgroundWorker pThis;
            public void Report(int progressPercent) { pThis.ReportProgress(progressPercent, this); }
        }

        void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            ProgressRpt r = (ProgressRpt)e.UserState;
            label1.Text = r.labelText;
            progressBar1.Value = e.ProgressPercentage;
        }

        void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            ProgressRpt r = new ProgressRpt();
            r.pThis = e.Argument as BackgroundWorker;
            // make a backup folder in the users docs folder
            String adtPath = txtPathToFile.Text;
            String adtFile = Path.GetFileName(adtPath);
            String docsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            DirectoryInfo di = Directory.CreateDirectory(Path.Combine(docsFolder, "eclipse_backup"));
            MessageBox.Show("Backups will be saved to " + di.ToString());
            // we want to copy the adt file to the backup folder
            String localCopy = Path.Combine(di.FullName, adtFile);
            r.labelText = "Copying Jar File to working directory";
            r.Report(1);
            File.Copy(adtPath, localCopy, true);
            r.Report(2);
            // we now need to unzip the file
            ZipFile zFile = new ZipFile(localCopy);
            String zipExtract = Path.Combine(Path.GetDirectoryName(localCopy),
                                                            Path.GetFileNameWithoutExtension(localCopy));
            if (Directory.Exists(zipExtract))
            {
                Directory.Delete(zipExtract, true);
            }
            DirectoryInfo di2 = Directory.CreateDirectory(zipExtract);
            r.labelText = "Extracting Jar contents";
            r.Report(10);
            pr = r;
            zFile.ExtractProgress += zFile_ExtractProgress;
            zFile.ExtractAll(zipExtract);
            r.Report(30);

            // we now need to work on the hardcoded file that contains our data to modify
            const string PATH_TO_CLASS = @"com\android\ide\eclipse\adt\internal\project";
            const string CLASS_FILE = "AndroidClasspathContainerInitializer.class";
            string FILE_PATH = Path.Combine(PATH_TO_CLASS, CLASS_FILE);
            if (Directory.Exists(Path.Combine(zipExtract, PATH_TO_CLASS))
                && File.Exists(Path.Combine(zipExtract, FILE_PATH)))
            {
                // ooo .NET 4.5 includes memory mapped files, how cool is that!
                MemoryMappedFile mm = MemoryMappedFile.CreateFromFile(Path.Combine(zipExtract, FILE_PATH),
                                                                      FileMode.Open,
                                                                      "adt_hack",
                                                                      0,
                                                                      MemoryMappedFileAccess.ReadWrite);
                MemoryMappedViewAccessor mva = mm.CreateViewAccessor();
                const string RES = "com/android/internal/**"; // this is the string we will be looking for
                byte[] raw = Encoding.ASCII.GetBytes(RES);
                byte[] buff = new byte[Encoding.ASCII.GetByteCount(RES)];
                long position = 0;
                byte readVal;
                bool foundString = false;
                int lastVal = 5;
                r.labelText = "Searching file for internals restriction string";
                while (position < mva.Capacity)
                {
                    int val = (int)(((double)position / mva.Capacity) * 100);
                    if (val > lastVal)
                    {
                        r.Report(val);
                        lastVal = val;
                    }
                    readVal = mva.ReadByte(position++);
                    if (readVal == raw[0])
                    {
                        long pstart = position;
                        // matches, so start trying to match the rest
                        int mp = 1;
                        readVal = mva.ReadByte(position++);
                        while (mp < raw.Length && readVal == raw[mp])
                        {
                            ++mp;
                            readVal = mva.ReadByte(position++);
                        }
                        if (mp == raw.Length)
                        {
                            // found a match
                            System.Diagnostics.Debug.WriteLine("Found string in file at " + pstart);
                            position = pstart;
                            foundString = true;
                        }
                        else
                        {
                            // we have to reset to the next char, since it may have been a bogus string and
                            // a match still exists.
                            position = pstart + 1;
                        }

                    }
                    if (foundString)
                    {
                        // we want to alter the string, we just change internal => internex
                        raw[raw.Length - 1 - 1 - 1] = Encoding.ASCII.GetBytes("x")[0];
                        for (int i = 0; i < raw.Length; i++)
                        {
                            mva.Write(position + i, raw[i]);
                        }
                        position += raw.Length;
                        foundString = false;
                        r.labelText = "Modified.. continuing check for any more";
                        r.Report(val);
                    }
                }
                mva.Dispose();
                mm.Dispose();
                Console.WriteLine("Altered file, now repacking");
                zFile.Dispose();
                r.labelText = "making backup of original jar with .bkp extension";
                r.Report(40);
                try
                {
                    if (File.Exists(localCopy + ".bkp"))
                    {
                        File.Delete(localCopy + ".bkp");
                    }
                    File.Move(localCopy, localCopy + ".bkp");
                }
                catch (IOException x)
                {
                    Console.WriteLine("Had an IO exception on file move " + x.ToString());
                }
                r.labelText = "Adding directory to new jar";
                r.Report(60);
                zFile = new ZipFile(localCopy);
                zFile.SaveProgress += zFile_SaveProgress;
                zFile.AddProgress += zFile_AddProgress;
                zFile.AddDirectory(zipExtract);
                r.labelText = "Saving Jar";
                r.Report(70);
                zFile.Save();
                zFile.Dispose();
                // Finally copy the Jar file back to where it started life, and can now allow access to the juicy
                // internal classes :0
                r.labelText = "Copying file back to eclipse directory from whence it came.";
                r.Report(98);
                File.Copy(localCopy, adtPath, true);
            }
            else
            {
                MessageBox.Show("The file " + FILE_PATH + " can't be found, this hack is not compatible.");
            }
            Console.WriteLine("Done");
            
        }

        ProgressRpt pr;
        double m_addedEntries = 0;
        void zFile_AddProgress(object sender, AddProgressEventArgs e)
        {
            if(e.EntriesTotal > 0)
                pr.Report((int)((++m_addedEntries / e.EntriesTotal) * 100));
        }

        void zFile_SaveProgress(object sender, SaveProgressEventArgs e)
        {
            if(e.TotalBytesToTransfer > 0)
                pr.Report((int)((e.BytesTransferred / (double)e.TotalBytesToTransfer) * 100));
        }

        void zFile_ExtractProgress(object sender, ExtractProgressEventArgs e)
        {
            if(e.TotalBytesToTransfer > 0)
                pr.Report((int)((e.BytesTransferred / (double)e.TotalBytesToTransfer) * 100));
        }

        private void btBrowse_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Eclipse plugins (*.jar)|*.jar|All files (*.*)|*.*";
            ofd.InitialDirectory = @"c:\";
            ofd.CheckFileExists = true;
            if (ofd.ShowDialog() != System.Windows.Forms.DialogResult.Cancel)
            {
                txtPathToFile.Text = ofd.FileName;
            }
        }
    }
}
